package asteroids;

import java.util.Random;

import be.kuleuven.cs.som.annotate.Basic;

/**
 * This class represents an asteroid of the arcade game Asteroids.
 * 
 * @author 	Antoine Lammens & Yannick Laevaert
 * @version	2.0
 *
 */
public class Asteroid extends SpaceEntity {
	
	/**
	 * Variable registering a random generator.
	 */
	private Random randomGenerator;

	/**
	 * Constant reflecting the mass density of an asteroid expressed in kilograms per cubic kilometer.
	 */
	private static final double ASTEROID_MASS_DENSITY = 2.65 * Math.pow(10, 12);

	/**
	 * Initialize this new asteroid with given values.
	 * 
	 * @param 	xPosition
	 * 			The x-position for this new asteroid.
	 * @param 	yPosition
	 * 			The y-position for this new asteroid.
	 * @param 	xVelocity
	 * 			The x-velocity for this new asteroid.
	 * @param 	yVelocity
	 * 			The y-velocity for this new asteroid.
	 * @param	radius
     * 			The radius for this new asteroid.
	 * @param	world
	 * 			The world of this new asteroid.
	 * @param	random
	 * 			The random generator of this new asteroid.
	 * @effect	| super(xPosition, yPosition, xVelocity, yVelocity, 4 / 3 * Math.PI * Math.pow(radius, 3) * ASTEROID_MASS_DENSITY, 
	 * 						radius, world)
	 * @post	| if(random != null) then new.getRandomGenerator == random
	 * 			| 					 else new.getRandomGenerator == new Random()
	 */
	public Asteroid(double xPosition, double yPosition, double xVelocity, double yVelocity, double radius, 
			World world, Random random) {
		super(xPosition, yPosition, xVelocity, yVelocity, 4 / 3 * Math.PI * Math.pow(radius, 3) * ASTEROID_MASS_DENSITY, 
				radius, world);
		if(random == null) {
			this.randomGenerator = new Random();
		}
		else {
			this.randomGenerator = random;
		}
	}

	/**
	 * Return the random generator.
	 */
	@Basic
	public Random getRandomGenerator() {
		return this.randomGenerator;
	}
	
	/**
	 * Override the method terminate in SpaceEntity. This method ensures that an asteroid spawns two smaller asteroids when its radius is greater 
	 * than or equal to 30 kilometres.
	 * 
	 * @post	| if(!isTerminated() && this.getWorld() != null && this.getRadius() < 30.0)
	 * 			|	then super.terminate()
	 * @post	| if(!isTerminated() && this.getWorld() != null && this.getRadius() >= 30.0)
	 * 			|	then this.getWorld().hasAsSpaceEntity(createChildAsteroid(positionAngle, velocityAngle, true) &&
	 * 			|		 this.getWorld().hasAsSpaceEntity(createChildAsteroid(positionAngle, velocityAngle, false) &&
	 * 			|		 super.terminate()
	 * @note	positionAngle and velocityAngle in the above specification are random chosen angles between 0 and 2*Pi radians.
	 * @throws	IllegalStateException
	 * 			| isTerminated() || this.getWorld() == null
	 */
	@Override
	public void terminate() throws IllegalStateException {
		if(!isTerminated() && this.getWorld() != null) {
			if(Util.fuzzyGreaterThanOrEqualTo(this.getRadius(), 30.0)) {
				World world = this.getWorld();
				double positionAngle = getRandomGenerator().nextDouble() * 2 * Math.PI;
				double velocityAngle = getRandomGenerator().nextDouble() * 2 * Math.PI;
				
				SpaceEntity childAsteroid1 = createChildAsteroid(positionAngle, velocityAngle, true);
				SpaceEntity childAsteroid2 = createChildAsteroid(positionAngle, velocityAngle, false);
				
				super.terminate();
				world.addSpaceEntity(childAsteroid1);
				world.addSpaceEntity(childAsteroid2);
				//To prevent asteroids to overlap.
				if(velocityAngle > positionAngle + Math.PI/2 && velocityAngle < positionAngle + 3*Math.PI/2
						|| velocityAngle + Math.PI*2 > positionAngle + Math.PI/2 && velocityAngle + Math.PI*2 < positionAngle + 3*Math.PI/2){
					world.resolveCollision(childAsteroid1, childAsteroid2, null);
				}
			}
			else {
				super.terminate();
			}
		}
		else
			throw new IllegalStateException();
	}
	
	/**
	 * Create a child asteroid of this asteroid.
	 * 
	 * @param 	positionAngle
	 * 			If firstChild is true, this is the angle at which the child asteroid is placed
	 * 			with reference to this asteroid, otherwise, the angle at which the child asteroid 
	 * 			is placed is this angle plus Pi.
	 * @param 	velocityAngle
	 * 			If firstChild is true, this is the angle at which the child asteroid starts to move,
	 * 			otherwise, the child asteroid moves in the opposite direction.
	 * @param 	firstChild
	 * 			Indicate whether the child asteroid is the first child or not.
	 * @return	The child Asteroid.
	 * 			| result.getRadius() == this.getRadius() / 2
	 * 			| result.getGeneralVelocity() == 1.5 * this.getGeneralVelocity()
	 * 			| result.getWorld() == this.getWorld()
	 * 			| result.getRandomGenerator() == this.getRandomGenerator()
	 * 			| if(firstChild)
	 * 			|	then result.getPosition().getX() == this.getPosition().getX() + this.getRadius()/2 * Math.cos(positionAngle) &&
	 * 			|		 result.getPosition().getY() == this.getPosition().getY() + this.getRadius()/2 * Math.sin(positionAngle) &&
	 * 			|		 result.getVelocity().getX() == 1.5 * this.getGeneralVelocity() * Math.cos(velocityAngle) &&
	 * 			|		 result.getVelocity().getY() == 1.5 * this.getGeneralVelocity() * Math.sin(velocityAngle)
	 * 			|	else result.getPosition().getX() == this.getPosition().getX() - this.getRadius()/2 * Math.cos(positionAngle) &&
	 * 			|		 result.getPosition().getY() == this.getPosition().getY() - this.getRadius()/2 * Math.sin(positionAngle) &&
	 * 			|		 result.getVelocity().getX() == - 1.5 * this.getGeneralVelocity() * Math.cos(velocityAngle) &&
	 * 			|		 result.getVelocity().getY() == - 1.5 * this.getGeneralVelocity() * Math.sin(velocityAngle)
	 * @effect	| if(result.getGeneralVelocity() > MAX_SPEED)
	 * 			|	then result.setMaxSpeedWithSameDirection()
	 */
	private Asteroid createChildAsteroid(double positionAngle, double velocityAngle, boolean firstChild){
		double currentVelocity = this.getGeneralVelocity(this.getVelocity().getX(), this.getVelocity().getY());
		double newVelocity = currentVelocity * 1.5;
		double xPosition, yPosition, xVelocity, yVelocity;
		if(firstChild){
			xPosition = this.getPosition().getX() + this.getRadius()/2 * Math.cos(positionAngle);
			yPosition = this.getPosition().getY() + this.getRadius()/2 * Math.sin(positionAngle);
			xVelocity = newVelocity * Math.cos(velocityAngle);
			yVelocity = newVelocity * Math.sin(velocityAngle);
		}
		else {
			xPosition = this.getPosition().getX() - this.getRadius()/2 * Math.cos(positionAngle);
			yPosition = this.getPosition().getY() - this.getRadius()/2 * Math.sin(positionAngle);
			xVelocity = - newVelocity * Math.cos(velocityAngle);
			yVelocity = - newVelocity * Math.sin(velocityAngle);
		}
		SpaceEntity childAsteroid = new Asteroid(xPosition, yPosition, xVelocity, yVelocity, 
				this.getRadius()/2, this.getWorld(), getRandomGenerator());
		if (!isValidVelocity(xVelocity, yVelocity))
			childAsteroid.setMaxSpeedWithSameDirection();
		return (Asteroid) childAsteroid;
	}
	
}
